Skip to content

Commit d992bd7

Browse files
Move pkgs/env.txt lockfile to conda-meta/initial-state.explicit.txt to preserve it (#1059)
Co-authored-by: Marco Esters <[email protected]>
1 parent c739975 commit d992bd7

File tree

10 files changed

+125
-67
lines changed

10 files changed

+125
-67
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,3 +159,6 @@ build/
159159

160160
# setuptools-scm
161161
constructor/_version.py
162+
163+
# Temporary workspaces
164+
tmp/

constructor/header.sh

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -598,8 +598,7 @@ CONDA_EXTRA_SAFETY_CHECKS=no \
598598
CONDA_CHANNELS="{{ channels }}" \
599599
CONDA_PKGS_DIRS="$PREFIX/pkgs" \
600600
CONDA_QUIET="$BATCH" \
601-
"$CONDA_EXEC" install --offline --file "$PREFIX/pkgs/env.txt" -yp "$PREFIX" $shortcuts {{ no_rcs_arg }} || exit 1
602-
rm -f "$PREFIX/pkgs/env.txt"
601+
"$CONDA_EXEC" install --offline --file "$PREFIX/conda-meta/initial-state.explicit.txt" -yp "$PREFIX" $shortcuts {{ no_rcs_arg }} || exit 1
603602
604603
{%- if has_conda %}
605604
mkdir -p "$PREFIX/envs"
@@ -638,8 +637,7 @@ for env_pkgs in "${PREFIX}"/pkgs/envs/*/; do
638637
CONDA_CHANNELS="$env_channels" \
639638
CONDA_PKGS_DIRS="$PREFIX/pkgs" \
640639
CONDA_QUIET="$BATCH" \
641-
"$CONDA_EXEC" install --offline --file "${env_pkgs}env.txt" -yp "$PREFIX/envs/$env_name" $env_shortcuts {{ no_rcs_arg }} || exit 1
642-
rm -f "${env_pkgs}env.txt"
640+
"$CONDA_EXEC" install --offline --file "$PREFIX/envs/$env_name/conda-meta/initial-state.explicit.txt" -yp "$PREFIX/envs/$env_name" $env_shortcuts {{ no_rcs_arg }} || exit 1
643641
done
644642
{%- endif %}
645643

constructor/nsis/main.nsi.tmpl

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1375,13 +1375,12 @@ Section "Install"
13751375
${Print} "Setting up the {{ env.name }} environment..."
13761376
SetDetailsPrint listonly
13771377

1378-
# List of packages to install
1379-
SetOutPath "{{ env.env_txt_dir }}"
1380-
File "{{ env.env_txt_abspath }}"
13811378

13821379
# A conda-meta\history file is required for a valid conda prefix
13831380
SetOutPath "{{ env.conda_meta }}"
13841381
File "{{ env.history_abspath }}"
1382+
# List of packages to install, as a lockfile
1383+
File "{{ env.lockfile_txt_abspath }}"
13851384

13861385
# Set channels
13871386
System::Call 'kernel32::SetEnvironmentVariable(t,t)i("CONDA_CHANNELS", "{{ channels }}").r0'
@@ -1391,21 +1390,17 @@ Section "Install"
13911390
# Run conda install
13921391
${If} $Ana_CreateShortcuts_State = ${BST_CHECKED}
13931392
${Print} "Installing packages for {{ env.name }}, creating shortcuts if necessary..."
1394-
push '"$INSTDIR\_conda.exe" install --offline -yp "{{ env.prefix }}" --file "{{ env.env_txt }}" {{ env.shortcuts }} {{ env.no_rcs_arg }}'
1393+
push '"$INSTDIR\_conda.exe" install --offline -yp "{{ env.prefix }}" --file "{{ env.lockfile_txt }}" {{ env.shortcuts }} {{ env.no_rcs_arg }}'
13951394
${Else}
13961395
${Print} "Installing packages for {{ env.name }}..."
1397-
push '"$INSTDIR\_conda.exe" install --offline -yp "{{ env.prefix }}" --file "{{ env.env_txt }}" --no-shortcuts {{ env.no_rcs_arg }}'
1396+
push '"$INSTDIR\_conda.exe" install --offline -yp "{{ env.prefix }}" --file "{{ env.lockfile_txt }}" --no-shortcuts {{ env.no_rcs_arg }}'
13981397
${EndIf}
13991398
push 'Failed to link extracted packages to {{ env.prefix }}!'
14001399
push 'WithLog'
14011400
SetDetailsPrint listonly
14021401
call AbortRetryNSExecWait
14031402
SetDetailsPrint both
14041403

1405-
# Cleanup {{ env.name }} env.txt
1406-
SetOutPath "$INSTDIR"
1407-
Delete "{{ env.env_txt }}"
1408-
14091404
# Restore shipped conda-meta\history for remapped
14101405
# channels and retain only the first transaction
14111406
SetOutPath "{{ env.conda_meta }}"

constructor/osx/run_installation.sh

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ fi
4545

4646
# Perform the conda install
4747
notify "Installing packages. This might take a few minutes."
48+
49+
# 'install' below will modify the history file in a way we don't want;
50+
# keep a copy to restore later
51+
cp "$PREFIX/conda-meta/history" "$PREFIX/conda-meta/history.bak"
52+
4853
# shellcheck disable=SC2086
4954
if ! \
5055
CONDA_REGISTER_ENVS="{{ register_envs }}" \
@@ -53,14 +58,13 @@ CONDA_SAFETY_CHECKS=disabled \
5358
CONDA_EXTRA_SAFETY_CHECKS=no \
5459
CONDA_CHANNELS={{ channels }} \
5560
CONDA_PKGS_DIRS="$PREFIX/pkgs" \
56-
"$CONDA_EXEC" install --offline --file "$PREFIX/pkgs/env.txt" -yp "$PREFIX" $shortcuts {{ no_rcs_arg }}; then
61+
"$CONDA_EXEC" install --offline --file "$PREFIX/conda-meta/initial-state.explicit.txt" -yp "$PREFIX" $shortcuts {{ no_rcs_arg }}; then
5762
echo "ERROR: could not complete the conda install"
5863
exit 1
5964
fi
6065

61-
# Move the prepackaged history file into place
62-
mv "$PREFIX/pkgs/conda-meta/history" "$PREFIX/conda-meta/history"
63-
rm -f "$PREFIX/env.txt"
66+
# Restore history file as provided by installer
67+
mv "$PREFIX/conda-meta/history.bak" "$PREFIX/conda-meta/history"
6468

6569
# Same, but for the extra environments
6670

@@ -73,8 +77,9 @@ for env_pkgs in "${PREFIX}"/pkgs/envs/*/; do
7377
fi
7478

7579
notify "Installing ${env_name} packages..."
76-
mkdir -p "$PREFIX/envs/$env_name/conda-meta"
77-
touch "$PREFIX/envs/$env_name/conda-meta/history"
80+
# 'install' below will modify the history file in a way we don't want;
81+
# keep a copy to restore later
82+
cp "$PREFIX/envs/$env_name/conda-meta/history" "$PREFIX/envs/$env_name/conda-meta/history.bak"
7883

7984
if [[ -f "${env_pkgs}channels.txt" ]]; then
8085
env_channels="$(cat "${env_pkgs}channels.txt")"
@@ -99,10 +104,10 @@ for env_pkgs in "${PREFIX}"/pkgs/envs/*/; do
99104
CONDA_EXTRA_SAFETY_CHECKS=no \
100105
CONDA_CHANNELS="$env_channels" \
101106
CONDA_PKGS_DIRS="$PREFIX/pkgs" \
102-
"$CONDA_EXEC" install --offline --file "${env_pkgs}env.txt" -yp "$PREFIX/envs/$env_name" $env_shortcuts {{ no_rcs_arg }} || exit 1
103-
# Move the prepackaged history file into place
104-
mv "${env_pkgs}/conda-meta/history" "$PREFIX/envs/$env_name/conda-meta/history"
105-
rm -f "${env_pkgs}env.txt"
107+
"$CONDA_EXEC" install --offline --file "$PREFIX/envs/$env_name/conda-meta/initial-state.explicit.txt" -yp "$PREFIX/envs/$env_name" $env_shortcuts {{ no_rcs_arg }} || exit 1
108+
109+
# Restore history file as provided by installer
110+
mv "$PREFIX/envs/$env_name/conda-meta/history.bak" "$PREFIX/envs/$env_name/conda-meta/history"
106111
done
107112

108113
# Cleanup!

constructor/osxpkg.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -563,7 +563,7 @@ def create(info, verbose=False):
563563
fresh_dir(SCRIPTS_DIR)
564564
pkgs_dir = join(prefix, "pkgs")
565565
os.makedirs(pkgs_dir)
566-
preconda.write_files(info, pkgs_dir)
566+
preconda.write_files(info, prefix)
567567
preconda.copy_extra_files(info.get("extra_files", []), prefix)
568568
# These are the user-provided scripts, maybe patched to have a shebang
569569
# They will be called by a wrapping script added later, if present

constructor/preconda.py

Lines changed: 46 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,12 @@
4848
except ImportError:
4949
import ruamel_json as json
5050

51-
files = ".constructor-build.info", "urls", "urls.txt", "env.txt"
51+
files = (
52+
"pkgs/.constructor-build.info",
53+
"pkgs/urls",
54+
"pkgs/urls.txt",
55+
"conda-meta/initial-state.explicit.txt",
56+
)
5257

5358

5459
def write_index_cache(info, dst_dir, used_packages):
@@ -135,8 +140,28 @@ def system_info():
135140
return out
136141

137142

138-
def write_files(info, dst_dir):
139-
with open(join(dst_dir, ".constructor-build.info"), "w") as fo:
143+
def write_files(info: dict, workspace: str):
144+
"""
145+
Prepare files on disk to be shipped as part of the pre-conda payload, mostly
146+
configuration and metadata files:
147+
148+
- `conda-meta/initial-state.explicit.txt`: Lockfile to provision the base environment.
149+
- `conda-meta/history`: Prepared history file with the right requested specs in input file.
150+
- `pkgs/urls` and `pkgs/urls.txt`: Direct URLs of packages used, with and without MD5 hashes.
151+
- `pkgs/cache/*.json`: Trimmed repodata to mock offline channels in use.
152+
- `pkgs/channels.txt`: Channels in use.
153+
- `pkgs/shortcuts.txt`: Which packages should have their shortcuts created, if any.
154+
155+
If extra envs are requested, this will also write:
156+
157+
- Their corresponding `envs/<env-name>/conda-meta/` files.
158+
- Their corresponding `pkgs/channels.txt` and `pkgs/shortcuts.txt` under
159+
`pkgs/envs/<env-name>`.
160+
"""
161+
os.makedirs(join(workspace, "conda-meta"), exist_ok=True)
162+
pkgs_dir = join(workspace, "pkgs")
163+
os.makedirs(pkgs_dir, exist_ok=True)
164+
with open(join(pkgs_dir, ".constructor-build.info"), "w") as fo:
140165
json.dump(system_info(), fo)
141166

142167
all_urls = info["_urls"].copy()
@@ -146,15 +171,15 @@ def write_files(info, dst_dir):
146171
final_urls_md5s = tuple((get_final_url(info, url), md5) for url, md5 in info["_urls"])
147172
all_final_urls_md5s = tuple((get_final_url(info, url), md5) for url, md5 in all_urls)
148173

149-
with open(join(dst_dir, "urls"), "w") as fo:
174+
with open(join(pkgs_dir, "urls"), "w") as fo:
150175
for url, md5 in all_final_urls_md5s:
151176
maybe_different_url = ensure_transmuted_ext(info, url)
152177
if maybe_different_url != url: # transmuted, no md5
153178
fo.write(f"{maybe_different_url}\n")
154179
else:
155180
fo.write(f"{url}#{md5}\n")
156181

157-
with open(join(dst_dir, "urls.txt"), "w") as fo:
182+
with open(join(pkgs_dir, "urls.txt"), "w") as fo:
158183
for url, _ in all_final_urls_md5s:
159184
fo.write("%s\n" % url)
160185

@@ -163,33 +188,36 @@ def write_files(info, dst_dir):
163188
all_dists += env_info["_dists"]
164189
all_dists = list({dist: None for dist in all_dists}) # de-duplicate
165190

166-
write_index_cache(info, dst_dir, all_dists)
191+
write_index_cache(info, pkgs_dir, all_dists)
167192

168193
# base environment conda-meta
169-
write_conda_meta(info, dst_dir, final_urls_md5s)
194+
write_conda_meta(info, join(workspace, "conda-meta"), final_urls_md5s)
170195

171-
write_repodata_record(info, dst_dir)
196+
write_repodata_record(info, pkgs_dir)
172197

173198
# base environment file used with conda install --file
174199
# (list of specs/dists to install)
175-
write_env_txt(info, dst_dir, final_urls_md5s)
200+
write_initial_state_explicit_txt(info, join(workspace, "conda-meta"), final_urls_md5s)
176201

177202
for fn in files:
178-
os.chmod(join(dst_dir, fn), 0o664)
203+
os.chmod(join(workspace, fn), 0o664)
179204

180205
for env_name, env_info in info.get("_extra_envs_info", {}).items():
181206
env_config = info["extra_envs"][env_name]
182-
env_dst_dir = os.path.join(dst_dir, "envs", env_name)
207+
env_pkgs = os.path.join(workspace, "pkgs", "envs", env_name)
208+
env_conda_meta = os.path.join(workspace, "envs", env_name, "conda-meta")
209+
os.makedirs(env_pkgs, exist_ok=True)
210+
os.makedirs(env_conda_meta, exist_ok=True)
183211
# environment conda-meta
184212
env_urls_md5 = tuple((get_final_url(info, url), md5) for url, md5 in env_info["_urls"])
185213
user_requested_specs = env_config.get("user_requested_specs", env_config.get("specs", ()))
186-
write_conda_meta(info, env_dst_dir, env_urls_md5, user_requested_specs)
214+
write_conda_meta(info, env_conda_meta, env_urls_md5, user_requested_specs)
187215
# environment installation list
188-
write_env_txt(info, env_dst_dir, env_urls_md5)
216+
write_initial_state_explicit_txt(info, env_conda_meta, env_urls_md5)
189217
# channels
190-
write_channels_txt(info, env_dst_dir, env_config)
218+
write_channels_txt(info, env_pkgs, env_config)
191219
# shortcuts
192-
write_shortcuts_txt(info, env_dst_dir, env_config)
220+
write_shortcuts_txt(info, env_pkgs, env_config)
193221

194222

195223
def write_conda_meta(info, dst_dir, final_urls_md5s, user_requested_specs=None):
@@ -212,9 +240,7 @@ def write_conda_meta(info, dst_dir, final_urls_md5s, user_requested_specs=None):
212240
builder.append("# update specs: %s" % update_specs)
213241
builder.append("\n")
214242

215-
if not isdir(join(dst_dir, "conda-meta")):
216-
os.makedirs(join(dst_dir, "conda-meta"))
217-
with open(join(dst_dir, "conda-meta", "history"), "w") as fh:
243+
with open(join(dst_dir, "history"), "w") as fh:
218244
fh.write("\n".join(builder))
219245

220246

@@ -245,7 +271,7 @@ def write_repodata_record(info, dst_dir):
245271
json.dump(rr_json, rf, indent=2, sort_keys=True)
246272

247273

248-
def write_env_txt(info, dst_dir, urls):
274+
def write_initial_state_explicit_txt(info, dst_dir, urls):
249275
"""
250276
urls is an iterable of tuples with url and md5 values
251277
"""
@@ -257,7 +283,7 @@ def write_env_txt(info, dst_dir, urls):
257283
@EXPLICIT
258284
"""
259285
).lstrip()
260-
with open(join(dst_dir, "env.txt"), "w") as envf:
286+
with open(join(dst_dir, "initial-state.explicit.txt"), "w") as envf:
261287
envf.write(header)
262288
for url, md5 in urls:
263289
maybe_different_url = ensure_transmuted_ext(info, url)

constructor/shar.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -131,15 +131,15 @@ def create(info, verbose=False):
131131
postconda_tarball = join(tmp_dir, "postconda.tar.bz2")
132132
pre_t = tarfile.open(preconda_tarball, "w:bz2")
133133
post_t = tarfile.open(postconda_tarball, "w:bz2")
134-
for dist in preconda_files:
135-
fn = filename_dist(dist)
136-
pre_t.add(join(tmp_dir, fn), "pkgs/" + fn)
134+
for rel_path in preconda_files:
135+
pre_t.add(join(tmp_dir, rel_path), rel_path)
137136

138137
for env_name in info.get("_extra_envs_info", ()):
139-
pre_t.add(join(tmp_dir, "envs", env_name, "env.txt"), f"pkgs/envs/{env_name}/env.txt")
140-
pre_t.add(
141-
join(tmp_dir, "envs", env_name, "shortcuts.txt"), f"pkgs/envs/{env_name}/shortcuts.txt"
142-
)
138+
for rel_path in (
139+
f"pkgs/envs/{env_name}/shortcuts.txt",
140+
f"envs/{env_name}/conda-meta/initial-state.explicit.txt",
141+
):
142+
pre_t.add(join(tmp_dir, rel_path), rel_path)
143143

144144
for key in "pre_install", "post_install":
145145
if key in info:
@@ -165,7 +165,7 @@ def create(info, verbose=False):
165165
elif filename_dist(dist).endswith(".tar.bz2"):
166166
_dist = filename_dist(dist)[:-8]
167167
record_file = join(_dist, "info", "repodata_record.json")
168-
record_file_src = join(tmp_dir, record_file)
168+
record_file_src = join(tmp_dir, "pkgs", record_file)
169169
record_file_dest = join("pkgs", record_file)
170170
pre_t.add(record_file_src, record_file_dest)
171171
pre_t.addfile(tarinfo=tarfile.TarInfo("conda-meta/history"))
@@ -185,7 +185,7 @@ def create(info, verbose=False):
185185
pre_t.close()
186186
post_t.close()
187187

188-
tarball = join(tmp_dir, "tmp.tar")
188+
tarball = join(tmp_dir, "pkgs", "tmp.tar")
189189
t = tarfile.open(tarball, "w")
190190
t.add(preconda_tarball, basename(preconda_tarball))
191191
t.add(postconda_tarball, basename(postconda_tarball))

constructor/winexe.py

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -81,11 +81,10 @@ def setup_envs_commands(info, dir_path):
8181
{
8282
"name": "base",
8383
"prefix": r"$INSTDIR",
84-
"env_txt": r"$INSTDIR\pkgs\env.txt", # env.txt as seen by the running installer
85-
"env_txt_dir": r"$INSTDIR\pkgs", # env.txt location in the installer filesystem
86-
"env_txt_abspath": join(
87-
dir_path, "env.txt"
88-
), # env.txt path while building the installer
84+
# initial-state.explicit.txt as seen by the running installer
85+
"lockfile_txt": r"$INSTDIR\conda-meta\initial-state.explicit.txt",
86+
# initial-state.explicit.txt path while building the installer
87+
"lockfile_txt_abspath": join(dir_path, "conda-meta", "initial-state.explicit.txt"),
8988
"conda_meta": r"$INSTDIR\conda-meta",
9089
"history_abspath": join(dir_path, "conda-meta", "history"),
9190
"final_channels": get_final_channels(info),
@@ -108,9 +107,12 @@ def setup_envs_commands(info, dir_path):
108107
{
109108
"name": env_name,
110109
"prefix": join("$INSTDIR", "envs", env_name),
111-
"env_txt": join("$INSTDIR", "pkgs", "envs", env_name, "env.txt"),
112-
"env_txt_dir": join("$INSTDIR", "pkgs", "envs", env_name),
113-
"env_txt_abspath": join(dir_path, "envs", env_name, "env.txt"),
110+
"lockfile_txt": join(
111+
"$INSTDIR", "envs", env_name, "conda-meta", "initial-state.explicit.txt"
112+
),
113+
"lockfile_txt_abspath": join(
114+
dir_path, "envs", env_name, "conda-meta", "initial-state.explicit.txt"
115+
),
114116
"conda_meta": join("$INSTDIR", "envs", env_name, "conda-meta"),
115117
"history_abspath": join(dir_path, "envs", env_name, "conda-meta", "history"),
116118
"final_channels": get_final_channels(channel_info),
@@ -169,20 +171,20 @@ def make_nsi(
169171
"outfile": info["_outpath"],
170172
"vipv": make_VIProductVersion(info["version"]),
171173
"constructor_version": info["CONSTRUCTOR_VERSION"],
174+
# @-prefixed paths point to {dir_path}
172175
"iconfile": "@icon.ico",
173176
"headerimage": "@header.bmp",
174177
"welcomeimage": "@welcome.bmp",
175178
"licensefile": abspath(info.get("license_file", join(NSIS_DIR, "placeholder_license.txt"))),
176179
"conda_history": "@" + join("conda-meta", "history"),
177180
"conda_exe": "@_conda.exe",
178-
"env_txt": "@env.txt",
179-
"urls_file": "@urls",
180-
"urls_txt_file": "@urls.txt",
181+
"urls_file": "@" + join("pkgs", "urls"),
182+
"urls_txt_file": "@" + join("pkgs", "urls.txt"),
181183
"pre_install": "@pre_install.bat",
182184
"post_install": "@post_install.bat",
183185
"pre_uninstall": "@pre_uninstall.bat",
184-
"index_cache": "@cache",
185-
"repodata_record": "@repodata_record.json",
186+
"index_cache": "@" + join("pkgs", "cache"),
187+
"repodata_record": "@" + join("pkgs", "repodata_record.json"),
186188
}
187189

188190
conclusion_text = info.get("conclusion_text", "")

news/1059-lockfiles

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
### Enhancements
2+
3+
* Ship `conda-meta/initial-state.explicit.txt` as a copy of the lockfile that provisions the initial state of each environment. (#1052 via #1059)
4+
5+
### Bug fixes
6+
7+
* <news item>
8+
9+
### Deprecations
10+
11+
* <news item>
12+
13+
### Docs
14+
15+
* <news item>
16+
17+
### Other
18+
19+
* <news item>

0 commit comments

Comments
 (0)