Flutter es un framework que me está gustando bastante, es muy consistente en cuanto a las distintas versiones, se ve muy similar en todas las plataformas y Dart como lenguaje es bastante interesante y nada esotérico (por eso de que usa cosas que ya manejamos y no se dedica a reinventar la rueda).
Tanto es así que empezamos hace unos meses un proyecto de aplicación móvil con Flutter, coincidiendo con la llegada de la versión 2.0 y las cosas empezaron a ir «demasiado deprisa». La comunidad y los mantenedores del sdk parece que se han puesto las pilas y han decidido incluir mejoras a un ritmo trepidante, cuando escribo estas líneas ya vamos por la versión 2.8.1.
Nosotros estabilizamos la app en un contenedor docker usando la versión 2.5.2 de Flutter y nos encontramos ahora que no hay manera de instalar desde cero esa versión con los sistemas que proporciona Flutter. Todos aquellos sistemas en los que hicimos un upgrade ya no los podemos utilizar para desarrollo porque, entre otras cosas, han cambiado librerías y ya no son compatibles algunas partes de nuestro código.
Pero no soy el único con ese problema. Varios de los desarrolladores de Flutter se han encontrado con la misma situación y, afortunadamente, han desarrollado una manera de poder disponer de una versión propia de flutter para cada proyecto… Mediante FVM.
Os cuento los pasos básicos para tener FVM funcionando (partiendo de que has instalado flutter en tu sistema) y cómo usarlo para cada proyecto:
1. Instalar FVM
dart pub global activate fvm
Esto te instala el paquete, pero tendrás que cambiar el entorno para poder acceder al comando fvm cada vez, en mi caso es algo como:
export PATH="$PATH":"$HOME/.pub-cache/bin"
Añadiendo esta línea al final del ~/.bashrc conseguiréis meter en el path el comando FVM
2. Instalar una versión de flutter para su uso posterior
En mi caso quería instalar la versión 2.5.2 y tuve que ejecutar esto:
fvm install 2.5.2
Puedes instalar tantas como quieras (o necesites), puedes conseguir la lista de todas las instaladas con el comando fvm list
3. Usar una versión concreta en tu proyecto
Dentro del directorio del proyecto en el que quieras usar esta versión puedes sustituir el uso del comando flutter por el comando fvm flutter que te ejecutará la versión correspondiente, para indicar qué versión quieres usar debes escribir:
fvm use 2.5.2
Esto te genera un directorio .fvm en donde se almacenarán los enlaces correspondientes, no olvides incluir en tu .gitignore el directorio .fvm/flutter_sdk
Con esto y algunas cosillas más (os dejo consultar la documentación) ya podréis desarrollar y depurar con la versión de flutter que queráis.
Durante estos días si te dedicas mínimamente a esto de la informática y has creado algún sistema con Java habras sufrido el problema del log4jshell. Si es que no, pues te lo explico:
log4j es una librería de Java que lleva existiendo desde hace innumerables años y que es muy utilizada para registrar los eventos que tienen lugar en un sistema y poder controlar qué se registra y qué no. Pues bien, la versión 2 de esta librería incluía funcionalidades para hacer sustituciones en las cadenas que se guardan en el log y, continuando con la tradición más añeja de Java, se les ocurrió que sería buena idea que esas cadenas a sustituir pudiesen hacer llamadas a sistemas externos y recibir objetos completos. Utilizando el estandar jndi permitían incluso hacer llamadas RMI o acceder a servidores LDAP. Supongo que el programador que añadió estas funcionalidades vería su utilidad en algún momento, pero también es cierto que yo nunca he visto a nadie usarla.
En fin, que esto llevaba ahí un tiempo y a algún «hacker malvado» se le ocurrió probar qué pasaba si en la url que le pasaba a la cadena a loguear incluía la dirección de un servidor que él controlaba y que permitía descargarse un objeto que hacía «cosas malas» en el sistema… Y la prueba funcionó y en cuanto alguien se dió cuenta que había cadenas extrañas que empezaban por ${jndi:ldap:// y que provocaban funcionamientos anómalos se dio la voz de alarma y se creó un registro de vulnerabilidad (CVE-2021-44228) y con un exploit super-sencillo y que mostraba que había cientos de miles de sistemas afectados.
El parche a esa vulnerabilidad llegó pronto (y eso que log4j está mantenido solo por voluntarios) y mucha gente se pasó buna parte del fin de semana parcheando los sistemas (alguno tardó algo más), aunque sin tener muy claro si el sistema había sido comprometido y modificado antes, cosa que requiere otras medidas adicionales y muy costosas. A día de hoy se ha encontrado que el parche que se hizo no contempla todos los casos y se ha registrado otra vulnerabilidad (CVE-2021-45046) que requiere otro parche… Parches, que, al fin y al cabo lo único que hacen es deshabilitar esa funcionalidad que, en algún momento, a alguien, le pareció interesante.
Son incontables el número de horas que el personal de IT (desde programadores hasta técnicos de sistemas) se han dedicado a tapar este agujero (y seguro que habrá muchos agujeros sin tapar todavía). Eso se traduce en muchos millones de Euros de dinero gastado sin sentido… Y todo porque a alguién «le pareció una buena idea esa funcionalidad» y porque todo el mundo usa componentes que no conoce, en los que «confía», aunque no poga un duro para su desarrollo…
Por cierto… Hay por ahí ya los «negacionistas de los parches» que van diciendo que si los parches son malos, que no saben lo que hay dentro, que todo es parte de una conspiración y esas cositas… En fin, el ser humano es lo que tiene (o no).
ACTUALIZACIÓN 17-12-2021: Hay todavía otro parche que meter, el 2.17.0 ya que hay una cadena especial que puede dar como resultado un DDoS… A ver lo que nos dura.
Últimamente estoy muy metido en esto del CI/CD (Integración continúa/Despliegue continuo) que no significa, ni más ni menos, que las actualizaciones a los repositorios de código generan automáticamente todos los artefactos necesarios para poder comprobar que están correctos y que pueden desplegarse en producción, e incluso desplegarlos si así lo consideramos.
En el caso de servicios en internet de cualquier tipo (webs, APIs, etc.) el método de despliegue suele ser más sencillo de automatizar (básicamente generas un contenedor con una versión nueva y lo «empujas» al sistema de producción), más sencillo incluso si utilizamos kubernetes o ansible. Pero en el caso de distribución de aplicaciones a instalar en clientes es más complicado. En el caso que traemos aquí lo que queremos hacer es subir a la tienda de google play (y ya veremos qué hacemos con apple) una nueva versión de una aplicación móvil.
En principio debería ser sencillo, ya que solo tenemos que utilizar el API de Google Play para desarrolladores. Pero antes, tenemos que conseguir ciertas «autorizaciones» de google que no son tan triviales como deberían. Para haceros la tarea un poco más sencilla, aquí os dejo un script python que hace justo lo que queremos: subir a la tienda nuestro apk recién compilado al canal alpha (upload_apks.py)
"""Uploads an apk to the alpha track."""
import argparse
from googleapiclient import discovery
import httplib2
from oauth2client.service_account import ServiceAccountCredentials
from oauth2client import client
TRACK = 'alpha' # Can be 'alpha', beta', 'production' or 'rollout'
SERVICE_ACCOUNT_EMAIL = (
'ENTER_YOUR_SERVICE_ACCOUNT_EMAIL_HERE@developer.gserviceaccount.com')
# Declare command-line flags.
argparser = argparse.ArgumentParser(add_help=False)
argparser.add_argument('package_name',
help='The package name. Example: com.android.sample')
argparser.add_argument('apk_file',
nargs='?',
default='test.apk',
help='The path to the APK file to upload.')
argparser.add_argument('key_file',
nargs='?',
default='key.json',
help='key in json format for service account')
argparser.add_argument('version',
nargs='?',
default='New version',
help='Version name')
def main():
# Process flags and read their values.
flags = argparser.parse_args()
package_name = flags.package_name
apk_file = flags.apk_file
key_file = flags.key_file
version = flags.version
credentials = ServiceAccountCredentials.from_json_keyfile_name(
key_file,
scopes='https://www.googleapis.com/auth/androidpublisher')
http = httplib2.Http()
http = credentials.authorize(http)
service = discovery.build('androidpublisher', 'v3', http=http)
try:
edit_request = service.edits().insert(body={}, packageName=package_name)
result = edit_request.execute()
edit_id = result['id']
apk_response = service.edits().apks().upload(
editId=edit_id,
packageName=package_name,
media_body=apk_file).execute()
print ('Version code {} has been uploaded'.format(apk_response['versionCode']))
track_response = service.edits().tracks().update(
editId=edit_id,
track=TRACK,
packageName=package_name,
body={u'releases': [{
u'name': version,
u'versionCodes': [apk_response['versionCode']],
u'status': u'completed',
}]}).execute()
print ('Track {} is set for release(s) {}' .format (
track_response['track'], str(track_response['releases'])))
commit_request = service.edits().commit(
editId=edit_id, packageName=package_name).execute()
print ('Edit "{}" has been committed'.format(commit_request['id']))
except client.AccessTokenRefreshError:
print ('The credentials have been revoked or expired, please re-run the '
'application to re-authorize')
if __name__ == '__main__':
main()
Antes de poder ejecutar este código deberás instalar algunas librerías (o ponerlas en el Dockerfile)
Y ahora la parte más pesada… Los datos a rellenar en pc-api.json, para ello deberás configurar un proyecto API en la consola cloud de Google y crear una cuenta de servicio… Este no es un proceso inmediato, pero hay buenas instrucciones en la página de google.
Luego hay que ir a la sección de la Google Play Console al apartado Acceso a API, donde deberás vincular el proyecto que acabas de crear y crear las cuentas de servicio necesarias.
Para después en la sección de Usuarios y permisos dar permisos a la cuenta de servicio que acabamos de crear, al menos estos:
El tema de extraer la clave privada y eso ya os lo dejo para después por si alguien tiene curiosidad…
ACTUALIZACIÓN 4/01/2022
Como ahora Google solo deja subir bundles (.aab) dejo aquí el código modificado para poder hacerlo (según el tipo de archivo que elijamos):
#!/usr/bin/env python3
#
# Copyright 2014 Marta Rodriguez.
# Modified by Jose Antonio Espinosa (2021-2022)
#
# Licensed under the Apache License, Version 2.0 (the 'License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Uploads an apk/aab to the alpha track."""
import argparse
from googleapiclient import discovery
import httplib2
from oauth2client.service_account import ServiceAccountCredentials
from oauth2client import client
import os.path
import mimetypes
mimetypes.add_type("application/octet-stream", ".apk")
mimetypes.add_type("application/octet-stream", ".aab")
TRACK = 'alpha' # Can be 'alpha', beta', 'production' or 'rollout'
SERVICE_ACCOUNT_EMAIL = (
'ENTER_YOUR_SERVICE_ACCOUNT_EMAIL_HERE@developer.gserviceaccount.com')
# Declare command-line flags.
argparser = argparse.ArgumentParser(add_help=False)
argparser.add_argument('package_name',
help='The package name. Example: com.android.sample')
argparser.add_argument('apk_file',
nargs='?',
default='test.apk',
help='The path to the APK file to upload.')
argparser.add_argument('key_file',
nargs='?',
default='key.json',
help='key in json format for service account')
argparser.add_argument('version',
nargs='?',
default='New version',
help='Version name')
def main():
# Process flags and read their values.
flags = argparser.parse_args()
package_name = flags.package_name
apk_file = flags.apk_file
key_file = flags.key_file
version = flags.version
extension = os.path.splitext(apk_file)[1]
# Create an httplib2.Http object to handle our HTTP requests and authorize it
# with the Credentials. Note that the first parameter, service_account_name,
# is the Email address created for the Service account. It must be the email
# address associated with the key that was created.
credentials = ServiceAccountCredentials.from_json_keyfile_name(
key_file,
scopes='https://www.googleapis.com/auth/androidpublisher')
http = httplib2.Http()
http = credentials.authorize(http)
service = discovery.build('androidpublisher', 'v3', http=http)
try:
edit_request = service.edits().insert(body={}, packageName=package_name)
result = edit_request.execute()
edit_id = result['id']
if extension == '.apk':
apk_response = service.edits().apks().upload(
editId=edit_id,
packageName=package_name,
media_body=apk_file).execute()
else:
apk_response = service.edits().bundles().upload(
editId=edit_id,
packageName=package_name,
media_body=apk_file).execute()
print ('Version code {} has been uploaded'.format(apk_response['versionCode']))
track_response = service.edits().tracks().update(
editId=edit_id,
track=TRACK,
packageName=package_name,
body={u'releases': [{
u'name': version,
u'versionCodes': [apk_response['versionCode']],
u'status': u'completed',
}]}).execute()
print ('Track {} is set for release(s) {}' .format (
track_response['track'], str(track_response['releases'])))
commit_request = service.edits().commit(
editId=edit_id, packageName=package_name).execute()
print ('Edit "{}" has been committed'.format(commit_request['id']))
except client.AccessTokenRefreshError:
print ('The credentials have been revoked or expired, please re-run the '
'application to re-authorize')
if __name__ == '__main__':
main()
Aunque yo no soy muy fan de apple en nada (como podréis ver si miráis un poco en este blog), el hecho es que tuve que gastarme una cantidad ingente de dinero en comprarme un macbook pro en 2011 para tener la oportunidad de empezar a programar aplicaciones para iOS. Entonces no había otra manera, ahora, por suerte, si que la hay como podéis ver en este mismo blog.
Es la mayor inversión que he hecho nunca en un ordenador y, aunque mac os no es que sea el mejor sistema del mundo, el hardware si que estaba muy cuidado, la carcasa en aluminio y el teclado retroiluminado y amplio (además de una pantalla muy brillante) me hicieron tenerlo como portatil principal durante muchos años. Pero la obsolescencia programa de apple terminaría por llamar a la puerta. Primero fue el chip de la GPU que, aparentemente por exceso de calor, terminó por romperse y me obligaron a comprarme toda una nueva placa madre (coste superior a un portatil completo que no fuese apple), pero es que no contentos con esto dejaron de actualizar el sistema operativo en High Sierra y ya me he perdido 3 grandes actualizaciones. Después de que la «nueva» GPU terminase por romperse de nuevo decidí dejar de usarlo por un tiempo… Hasta ahora que he decidido ponerle un arranque dual y tener ubuntu y os x (lo que me permita apple) en el mismo ordenador (ahora mismo estoy escribiendo esto desde ubuntu en el macbook pro). Os comento en este post algunas cosas a hacer para resucitar un viejo macbook pro 2011 con la GPU radeon rota.
Lo primero es poder arrancar una vez que la tarjeta gráfica se ha estropeado, para ello lo que hay que hacer es arrancar en modo superusuario (arrancar con cmd+s pulsado) y en esa terminal escribir:
Una vez reiniciado ya con la pantalla normal (esto es temporal) habría que hacerlo un poco más permanente, para ello he creado un script que podríais lanzar desde cualquier distribución live (yo uso ubuntu) y que de hecho yo tengo almacenada en el usb arrancable (en la particion writable). El script es este:
echo "Ejecutar como root este script" printf "\x07\x00\x00\x00\x01\x00\x00\x00" > /sys/firmware/efi/efivars/gpu-power-prefs-fa4ce28d-b62f-4c99-9cc3-6815686e30f9 chattr +i "/sys/firmware/efi/efivars/gpu-power-prefs-fa4ce28d-b62f-4c99-9cc3-6815686e30f9" cd / umount /sys/firmware/efi/efivars/ echo "Ahora deberías reiniciar la máquina para que tenga efecto"
Arrancar con la live (tener pulsado la tecla alt mientras se arranca y luego seleccionar el disco arrancable de ubuntu) y ejecutar como root el script. Esto hace que el cambio sea permanente (hasta que se actualice de nuevo la información de la EFI, pero eso solo pasa con las instalaciones grandes del sistema operativo).
Lo siguiente ya es más entretenido… Actualizar el hardware (yo le he puesto dos ssd y estoy pensando en actualizar la memoria), instalar linux, intentar actualizar high sierra a otro sistema (yo he conseguido instalarle mojave), limpiarle por dentro y cambiar la pasta térmica (esto no es tan complicado pero hay que lidiar con la forma de integrar componentes que tiene mac). En fin, entretenimiento para rato.
Por suerte, parece que todo funciona (menos la maldita GPU de AMD) y puedo volver a usar el trasto cuando quiera.
Hay pocas cosas en informática que no me apetezcan hacer, pero una de ellas es tener que instalar windows en un ordenador. Como profesional que soy utilizo las herramientas que mejor vienen a mi trabajo y esas, desde hace más de 14 años viene siendo Linux, pero, en fín, si algún cliente tiene el capricho o, como en este caso si alguno de mis hijos se emperra por tener lo mismo que sus colegas, pues bueno, hay que ponerse.
El caso es que tras adquirir un portatil HP 15S-fq2005ns Intel Core i5-1135G7/8GB/512GB SSD/15.6″ en una tienda en la que dije que no iba a comprar nunca más descubro que el ordenador no trae sistema operativo (demasiado bien de precio, ya me parecía), así que desempolvo mi viejo usb instalador de windows y la licencia que tengo como partner de microsoft que es mi empresa y me pongo al tajo…
Primer paso, bien, segundo paso, bien, meto el número de licencia y luego… Me sale algo como esto:
¡Vaya por Dios! que no tengo disco… Salgo a la bios, encuentro que el disco es un NVMe 2.0 de 500Gb y que está ahí tan pancho. Y entonces es cuando pierdo un montón de horas en buscar en todos los sitios posibles una solución a esto… Os voy a ahorrar el sufrimiento y paso directamente a lo que tenéis que hacer si se os da el caso. El problema, aparentemente, es que teniendo un procesador de 11ª generación y un NVMe pues windows no te lo iba a reconocer si no le poníamos los drivers antes, así que lo que hice fue lo siguiente:
Descargar los drivers de Intel Rapid Storage Technology (IRST), por ejemplo, del propio intel. El archivo se llama f6flpy-x64.zip
Descomprimir el contenido del zip en un usb (o en el mismo usb de instalación dentro de un directorio)
En la pantalla en la que windows no nos encuentra el disco pulsar en «Cargar controlador» y luego en «examinar»
Seleccionar la unidad y el directorio donde hemos extraído los archivos y seleccionar el driver que pone Intel RST VMD Controller 9A08 (TGL)
Cuando se completa la instalación ya veréis, por fin, vuestro disco y podréis continuar con la instalación
Algunas notas:
Estos drivers solo funcionan con Windows 10 20H2 y más modernos., Yo me tiré un par de horas intentando que funcionase en mi usb instalador de una versión anterior y no hubo manera.
En la página de HP hay otros drivers que podemos utilizar (sp108979.exe), pero generan varios directorios de drivers y no estoy muy seguro de cuantos de ellos hay que cargar
Ya se que esto es obvio, pero Ubuntu 20.04 se instaló a la primera detectando correctamente disco y todo…
Espero no tener que repetir la experiencia próximamente (esa y la hora y media de actualizaciones de drivers que siguieron a la instalación principal), pero bueno, para que luego no se diga que no uso windows.
Si, es un coñazo, pero tengo que ponerte este aviso sobre las cookies y mi
Funcional
Siempre activo
El almacenamiento o acceso técnico es estrictamente necesario para el propósito legítimo de permitir el uso de un servicio específico explícitamente solicitado por el abonado o usuario, o con el único propósito de llevar a cabo la transmisión de una comunicación a través de una red de comunicaciones electrónicas.
Preferencias
El almacenamiento o acceso técnico es necesario para la finalidad legítima de almacenar preferencias no solicitadas por el abonado o usuario.
Estadísticas
El almacenamiento o acceso técnico que es utilizado exclusivamente con fines estadísticos.El almacenamiento o acceso técnico que se utiliza exclusivamente con fines estadísticos anónimos. Sin un requerimiento, el cumplimiento voluntario por parte de tu Proveedor de servicios de Internet, o los registros adicionales de un tercero, la información almacenada o recuperada sólo para este propósito no se puede utilizar para identificarte.
Marketing
El almacenamiento o acceso técnico es necesario para crear perfiles de usuario para enviar publicidad, o para rastrear al usuario en una web o en varias web con fines de marketing similares.