Skip to content

Commit

Permalink
feat: unmanaged installs
Browse files Browse the repository at this point in the history
The new "unmanaged" installation mode allows for something close
to "untar and move on" installations. It disables modifying the
PATH and creation of environment files, and ensures all files are
installed into a single flat directory no matter what other
configuration is in place. It also disables installing a standalone
updater and the creation of an install receipt.

The new "don't install an updater/receipt" behaviour is also
controllable behind a new public environment variable,
$APPNAME_DISABLE_UPDATE.

Fixes #1400.
  • Loading branch information
mistydemeo committed Sep 16, 2024
1 parent 26a352f commit 20f4c94
Show file tree
Hide file tree
Showing 60 changed files with 4,009 additions and 1,124 deletions.
34 changes: 29 additions & 5 deletions cargo-dist-schema/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,19 @@ pub struct SystemInfo {
pub build_environment: BuildEnvironment,
}

/// Release-specific environment variables
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct EnvironmentVariables {
/// Environment variable to force an install location
pub install_dir_env_var: String,
/// Environment variable to force an unmanaged install location
pub unmanaged_dir_env_var: String,
/// Environment variable to disable updater features
pub disable_update_env_var: String,
/// Environment variable to disable modifying the path
pub no_modify_path_env_var: String,
}

/// A Release of an Application
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct Release {
Expand All @@ -291,10 +304,10 @@ pub struct Release {
/// The version of the app
// FIXME: should be a Version but JsonSchema doesn't support (yet?)
pub app_version: String,
/// Environment variable to force an install location
/// Environment variables which control this release's installer's behaviour
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub install_dir_env_var: Option<String>,
pub env: Option<EnvironmentVariables>,
/// Alternative display name that can be prettier
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
Expand Down Expand Up @@ -610,12 +623,23 @@ impl DistManifest {
if let Some(position) = self.releases.iter().position(|r| r.app_name == name) {
&mut self.releases[position]
} else {
let install_dir_env_var =
Some(name.to_ascii_uppercase().replace('-', "_") + "_INSTALL_DIR");
let env_app_name = name.to_ascii_uppercase().replace('-', "_");
let install_dir_env_var = format!("{env_app_name}_INSTALL_DIR");
let unmanaged_dir_env_var = format!("{env_app_name}_UNMANAGED_INSTALL");
let disable_update_env_var = format!("{env_app_name}_DISABLE_UPDATE");
let no_modify_path_env_var = format!("{env_app_name}_NO_MODIFY_PATH");

let environment_variables = EnvironmentVariables {
install_dir_env_var,
unmanaged_dir_env_var,
disable_update_env_var,
no_modify_path_env_var,
};

self.releases.push(Release {
app_name: name,
app_version: version,
install_dir_env_var,
env: Some(environment_variables),
artifacts: vec![],
hosting: Hosting::default(),
display: None,
Expand Down
41 changes: 34 additions & 7 deletions cargo-dist-schema/src/snapshots/cargo_dist_schema__emit.snap
Original file line number Diff line number Diff line change
Expand Up @@ -639,6 +639,29 @@ expression: json_schema
}
}
},
"EnvironmentVariables": {
"description": "Release-specific environment variables",
"type": "object",
"required": [
"disable_update_env_var",
"install_dir_env_var",
"unmanaged_dir_env_var"
],
"properties": {
"disable_update_env_var": {
"description": "Environment variable to disable updater features",
"type": "string"
},
"install_dir_env_var": {
"description": "Environment variable to force an install location",
"type": "string"
},
"unmanaged_dir_env_var": {
"description": "Environment variable to force an unmanaged install location",
"type": "string"
}
}
},
"GithubCiInfo": {
"description": "Github CI backend",
"type": "object",
Expand Down Expand Up @@ -970,20 +993,24 @@ expression: json_schema
"null"
]
},
"env": {
"description": "Environment variables which control this release's installer's behaviour",
"anyOf": [
{
"$ref": "#/definitions/EnvironmentVariables"
},
{
"type": "null"
}
]
},
"hosting": {
"description": "Hosting info",
"allOf": [
{
"$ref": "#/definitions/Hosting"
}
]
},
"install_dir_env_var": {
"description": "Environment variable to force an install location",
"type": [
"string",
"null"
]
}
}
},
Expand Down
6 changes: 6 additions & 0 deletions cargo-dist/src/backend/installer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@ pub struct InstallerInfo {
pub platform_support: Option<PlatformSupport>,
/// Environment variable to force an install location
pub install_dir_env_var: String,
/// Like the above, but for unmanaged installs
pub unmanaged_dir_env_var: String,
/// Environment variable to disable self-update features
pub disable_update_env_var: String,
/// Environment variable to disable modifying the path
pub no_modify_path_env_var: String,
}

/// A fake fragment of an ExecutableZip artifact for installers
Expand Down
36 changes: 30 additions & 6 deletions cargo-dist/src/tasks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1684,10 +1684,16 @@ impl<'pkg_graph> DistGraphBuilder<'pkg_graph> {
.manifest
.release_by_name(&release.app_name)
.expect("couldn't find the release!?");
let install_dir_env_var = schema_release
.install_dir_env_var
.to_owned()

let env_vars = schema_release
.env
.as_ref()
.expect("couldn't determine app-specific environment variable!?");
let install_dir_env_var = env_vars.install_dir_env_var.to_owned();
let unmanaged_dir_env_var = env_vars.unmanaged_dir_env_var.to_owned();
let disable_update_env_var = env_vars.disable_update_env_var.to_owned();
let no_modify_path_env_var = env_vars.no_modify_path_env_var.to_owned();

let download_url = schema_release
.artifact_download_url()
.expect("couldn't compute a URL to download artifacts from!?");
Expand Down Expand Up @@ -1745,6 +1751,9 @@ impl<'pkg_graph> DistGraphBuilder<'pkg_graph> {
runtime_conditions,
platform_support: None,
install_dir_env_var,
unmanaged_dir_env_var,
disable_update_env_var,
no_modify_path_env_var,
})),
is_global: true,
};
Expand Down Expand Up @@ -1907,6 +1916,9 @@ impl<'pkg_graph> DistGraphBuilder<'pkg_graph> {
platform_support: None,
// Not actually needed for this installer type
install_dir_env_var: String::new(),
unmanaged_dir_env_var: String::new(),
disable_update_env_var: String::new(),
no_modify_path_env_var: String::new(),
},
install_libraries: config.install_libraries.clone(),
})),
Expand All @@ -1933,10 +1945,16 @@ impl<'pkg_graph> DistGraphBuilder<'pkg_graph> {
.manifest
.release_by_name(&release.app_name)
.expect("couldn't find the release!?");
let install_dir_env_var = schema_release
.install_dir_env_var
.to_owned()

let env_vars = schema_release
.env
.as_ref()
.expect("couldn't determine app-specific environment variable!?");
let install_dir_env_var = env_vars.install_dir_env_var.to_owned();
let unmanaged_dir_env_var = env_vars.unmanaged_dir_env_var.to_owned();
let disable_update_env_var = env_vars.disable_update_env_var.to_owned();
let no_modify_path_env_var = env_vars.no_modify_path_env_var.to_owned();

let download_url = schema_release
.artifact_download_url()
.expect("couldn't compute a URL to download artifacts from!?");
Expand Down Expand Up @@ -1990,6 +2008,9 @@ impl<'pkg_graph> DistGraphBuilder<'pkg_graph> {
runtime_conditions: RuntimeConditions::default(),
platform_support: None,
install_dir_env_var,
unmanaged_dir_env_var,
disable_update_env_var,
no_modify_path_env_var,
})),
is_global: true,
};
Expand Down Expand Up @@ -2104,6 +2125,9 @@ impl<'pkg_graph> DistGraphBuilder<'pkg_graph> {
platform_support: None,
// Not actually needed for this installer type
install_dir_env_var: String::new(),
unmanaged_dir_env_var: String::new(),
disable_update_env_var: String::new(),
no_modify_path_env_var: String::new(),
},
})),
is_global: true,
Expand Down
50 changes: 35 additions & 15 deletions cargo-dist/templates/installer/installer.ps1.j2
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,18 @@ $receipt = @"
"@
$receipt_home = "${env:LOCALAPPDATA}\{{ app_name }}"

if ($env:{{ disable_update_env_var }}) {
$install_updater = $false
} else {
$install_updater = $true
}

$unmanaged_install = $env:{{ unmanaged_dir_env_var }}
if ($unmanaged_install) {
$NoModifyPath = $true
$install_updater = $false
}

function Install-Binary($install_args) {
if ($Help) {
Get-Help $PSCommandPath -Detailed
Expand Down Expand Up @@ -219,7 +231,7 @@ function Download($download_url, $platforms) {
$staticlib_paths += "$tmp\$lib_name"
}

if ($null -ne $info["updater"]) {
if (($null -ne $info["updater"]) -and $install_updater) {
$updater_id = $info["updater"]["artifact_name"]
$updater_url = "$download_url/$updater_id"
$out_name = "$tmp\{{ app_name }}-update.exe"
Expand Down Expand Up @@ -248,12 +260,18 @@ function Invoke-Installer($artifacts, $platforms) {

# Forces the install to occur at this path, not the default
$force_install_dir = $null
$flat_install_layout = $false
# Check the newer app-specific variable before falling back
# to the older generic one
if (($env:{{ install_dir_env_var }})) {
$force_install_dir = $env:{{ install_dir_env_var }}
$flat_install_layout = {% if install_paths | selectattr("kind", "equalto", "CargoHome") -%} $false {%- else -%} $true {%- endif %}
} elseif (($env:CARGO_DIST_FORCE_INSTALL_DIR)) {
$force_install_dir = $env:CARGO_DIST_FORCE_INSTALL_DIR
$flat_install_layout = {% if install_paths | selectattr("kind", "equalto", "CargoHome") -%} $false {%- else -%} $true {%- endif %}
} elseif ($unmanaged_install) {
$force_install_dir = $unmanaged_install
$flat_install_layout = $true
}

# The actual path we're going to install to
Expand All @@ -267,13 +285,13 @@ function Invoke-Installer($artifacts, $platforms) {
# Before actually consulting the configured install strategy, see
# if we're overriding it.
if (($force_install_dir)) {
{% if install_paths| selectattr("kind", "equalto", "CargoHome") %}
$dest_dir = Join-Path $force_install_dir "bin"
$dest_dir_lib = $dest_dir
{%- else -%}
$dest_dir = $force_install_dir
$dest_dir_lib = $dest_dir
{%- endif %}
if (-not $flat_install_layout) {
$dest_dir = Join-Path $force_install_dir "bin"
$dest_dir_lib = $dest_dir
} else {
$dest_dir = $force_install_dir
$dest_dir_lib = $dest_dir
}
$receipt_dest_dir = $force_install_dir
}
{%- for install_path in install_paths %}
Expand Down Expand Up @@ -361,13 +379,15 @@ function Invoke-Installer($artifacts, $platforms) {
$receipt = $receipt.Replace('"binary_aliases":{}', -join('"binary_aliases":', $info['aliases_json']))

# Write the install receipt
$null = New-Item -Path $receipt_home -ItemType "directory" -ErrorAction SilentlyContinue
# Trying to get Powershell 5.1 (not 6+, which is fake and lies) to write utf8 is a crime
# because "Out-File -Encoding utf8" actually still means utf8BOM, so we need to pull out
# .NET's APIs which actually do what you tell them (also apparently utf8NoBOM is the
# default in newer .NETs but I'd rather not rely on that at this point).
$Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $False
[IO.File]::WriteAllLines("$receipt_home/{{ app_name }}-receipt.json", "$receipt", $Utf8NoBomEncoding)
if ($install_updater) {
$null = New-Item -Path $receipt_home -ItemType "directory" -ErrorAction SilentlyContinue
# Trying to get Powershell 5.1 (not 6+, which is fake and lies) to write utf8 is a crime
# because "Out-File -Encoding utf8" actually still means utf8BOM, so we need to pull out
# .NET's APIs which actually do what you tell them (also apparently utf8NoBOM is the
# default in newer .NETs but I'd rather not rely on that at this point).
$Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $False
[IO.File]::WriteAllLines("$receipt_home/{{ app_name }}-receipt.json", "$receipt", $Utf8NoBomEncoding)
}

# Respect the environment, but CLI takes precedence
if ($null -eq $NoModifyPath) {
Expand Down
Loading

0 comments on commit 20f4c94

Please sign in to comment.