Skip to content

Commit

Permalink
ufbt-bootstrap: added dotenv_create command; ufbt: added support fo…
Browse files Browse the repository at this point in the history
…r .env files in project directories to specify ufbt state location & other env variables
  • Loading branch information
hedger committed Dec 18, 2023
1 parent de5a03f commit bb2437d
Show file tree
Hide file tree
Showing 3 changed files with 127 additions and 7 deletions.
2 changes: 1 addition & 1 deletion VERSION.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.2.4.4
0.2.5
28 changes: 28 additions & 0 deletions ufbt/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,42 @@
bootstrap_subcommands,
get_ufbt_package_version,
DEFAULT_UFBT_HOME,
ENV_FILE_NAME,
)

__version__ = get_ufbt_package_version()


def _load_env_file(env_file):
"""
Minimalistic implementation of env file parser.
Only supports lines in format `KEY=VALUE`.
Ignores comments (lines starting with #) and empty lines.
"""
if not os.path.exists(env_file):
return {}
env_vars = {}
with open(env_file) as f:
for line in f:
line = line.strip()
if not line or line.startswith("#"):
continue
key, value = line.split("=", 1)
env_vars[key] = value
return env_vars


def ufbt_cli():
# load environment variables from .env file in current directory
env_vars = _load_env_file(ENV_FILE_NAME)
if env_vars:
os.environ.update(env_vars)

if not os.environ.get("UFBT_HOME"):
os.environ["UFBT_HOME"] = DEFAULT_UFBT_HOME

os.environ["UFBT_HOME"] = os.path.abspath(os.environ["UFBT_HOME"])

# ufbt impl uses UFBT_STATE_DIR internally, not UFBT_HOME
os.environ["UFBT_STATE_DIR"] = os.environ["UFBT_HOME"]
if not os.environ.get("FBT_TOOLCHAIN_PATH"):
Expand Down
104 changes: 98 additions & 6 deletions ufbt/bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import json
import logging
import os
import platform
import re
import shutil
import sys
Expand All @@ -38,6 +39,8 @@

log = logging.getLogger(__name__)
DEFAULT_UFBT_HOME = os.path.expanduser("~/.ufbt")
ENV_FILE_NAME = ".env"
STATE_DIR_TOOLCHAIN_SUBDIR = "toolchain"


def get_ufbt_package_version():
Expand Down Expand Up @@ -493,14 +496,19 @@ def create_for_task(task: SdkDeployTask, download_dir: str) -> BaseSdkLoader:
class UfbtSdkDeployer:
UFBT_STATE_FILE_NAME = "ufbt_state.json"

def __init__(self, ufbt_state_dir: str):
def __init__(self, ufbt_state_dir: str, toolchain_dir: str = None):
self.ufbt_state_dir = Path(ufbt_state_dir)
self.download_dir = self.ufbt_state_dir / "download"
self.current_sdk_dir = self.ufbt_state_dir / "current"
self.toolchain_dir = (
Path(os.environ.get("FBT_TOOLCHAIN_PATH", self.ufbt_state_dir.absolute()))
/ "toolchain"
)
if toolchain_dir:
self.toolchain_dir = self.ufbt_state_dir / toolchain_dir
else:
self.toolchain_dir = (
Path(
os.environ.get("FBT_TOOLCHAIN_PATH", self.ufbt_state_dir.absolute())
)
/ STATE_DIR_TOOLCHAIN_SUBDIR
)
self.state_file = self.current_sdk_dir / self.UFBT_STATE_FILE_NAME

def get_previous_task(self) -> Optional[SdkDeployTask]:
Expand Down Expand Up @@ -582,6 +590,9 @@ def __init__(self):
super().__init__(self.COMMAND, "Update uFBT SDK")

def _add_arguments(self, parser: argparse.ArgumentParser) -> None:
parser.description = """Update uFBT SDK. By default uses the last used target and mode.
Otherwise deploys latest release."""

parser.add_argument(
"--hw-target",
"-t",
Expand Down Expand Up @@ -611,6 +622,8 @@ def __init__(self):
super().__init__(self.COMMAND, "Clean uFBT SDK state")

def _add_arguments(self, parser: argparse.ArgumentParser):
parser.description = """Clean up uFBT internal state. By default cleans current SDK state.
For cleaning app build artifacts, use 'ufbt -c' instead."""
parser.add_argument(
"--downloads",
help="Clean downloads",
Expand Down Expand Up @@ -662,6 +675,8 @@ def __init__(self):
super().__init__(self.COMMAND, "Show uFBT SDK status")

def _add_arguments(self, parser: argparse.ArgumentParser) -> None:
parser.description = """Show uFBT status - deployment paths and SDK version."""

parser.add_argument(
"--json",
help="Print status in JSON format",
Expand Down Expand Up @@ -723,7 +738,84 @@ def _func(self, args) -> int:
return 0


bootstrap_subcommand_classes = (UpdateSubcommand, CleanSubcommand, StatusSubcommand)
class LocalEnvSubcommand(CliSubcommand):
COMMAND = "dotenv_create"

def __init__(self):
super().__init__(self.COMMAND, "Create a local environment for uFBT")

def _add_arguments(self, parser: argparse.ArgumentParser) -> None:
parser.description = f"""Create a dotenv ({ENV_FILE_NAME}) file in current directory with environment variables for uFBT.
Designed for per-project SDK management.
If {ENV_FILE_NAME} file already exists, this command will refuse to overwrite it.
"""
parser.add_argument(
"--state-dir",
help="Directory to create the local environment in. Defaults to '.ufbt'.",
default=".ufbt",
)

parser.add_argument(
"--no-link-toolchain",
help="Don't link toolchain directory to the local environment and create a local copy",
action="store_true",
default=False,
)

@staticmethod
def _link_dir(target_path, source_path):
log.info(f"Linking {target_path=} to {source_path=}")
if os.path.lexists(target_path) or os.path.exists(target_path):
os.unlink(target_path)
if platform.system() == "Windows":
# Crete junction - does not require admin rights
import _winapi

if not os.path.isdir(source_path):
raise ValueError(f"Source path {source_path} is not a directory")

if not os.path.exists(target_path):
_winapi.CreateJunction(source_path, target_path)
else:
os.symlink(source_path, target_path)

def _func(self, args) -> int:
if os.path.exists(ENV_FILE_NAME):
log.error(
f"File {ENV_FILE_NAME} already exists, refusing to overwrite. Please remove or update it manually."
)
return 1

env_sdk_deployer = UfbtSdkDeployer(args.state_dir, STATE_DIR_TOOLCHAIN_SUBDIR)
# Will extract toolchain dir from env
default_sdk_deployer = UfbtSdkDeployer(args.ufbt_home)

env_sdk_deployer.ufbt_state_dir.mkdir(parents=True, exist_ok=True)
if not args.no_link_toolchain:
env_sdk_deployer.ufbt_state_dir.mkdir(parents=True, exist_ok=True)
self._link_dir(
str(env_sdk_deployer.toolchain_dir.absolute()),
str(default_sdk_deployer.toolchain_dir.absolute()),
)

env_vars = {
"UFBT_HOME": args.state_dir,
# "TOOLCHAIN_PATH": str(env_sdk_deployer.toolchain_dir.absolute()),
}

with open(ENV_FILE_NAME, "wt") as f:
for key, value in env_vars.items():
f.write(f"{key}={value}\n")

return 0


bootstrap_subcommand_classes = (
UpdateSubcommand,
CleanSubcommand,
StatusSubcommand,
LocalEnvSubcommand,
)

bootstrap_subcommands = (
subcommand_cls.COMMAND for subcommand_cls in bootstrap_subcommand_classes
Expand Down

0 comments on commit bb2437d

Please sign in to comment.