build: compile/watch sass with new npm scripts
`paver` commands are deprecated for managing static assets. Starting in Sumac, only `npm run` commands will be supported for managing static assets. To ease the transition, both `paver` and `npm run` commands will work in Redwood. However, we want to stop using the *implementations* of the `paver` asset commands right now, as they are blocking the Python 3.11 upgrade. This will also make the removal of `paver` commands more straightforward come Sumac. So, this commit turns these commands/functions: * paver compile_sass (used by configuration) * paver watch_sass (used by configuration and devstack) * pavelib/assets.py:_compile_sass (used by Tutor) into very thin wrappers around the new `npm run` commands. Each of these paver routines now raise a loud deprecation warning, including a message of the `npm run` command that the operator can switch to. We expect no impact to site operators or end users. https://github.com/openedx/edx-platform/issues/31895
This commit is contained in:
committed by
Kyle McCormick
parent
bd82b1d75a
commit
24db4dfb53
@@ -1,75 +0,0 @@
|
||||
"""
|
||||
This file contains helpers for paver commands, Django is not initialized in paver commands.
|
||||
So, django settings, models etc. can not be used here.
|
||||
"""
|
||||
|
||||
|
||||
import os
|
||||
|
||||
from path import Path
|
||||
|
||||
|
||||
def get_theme_paths(themes, theme_dirs):
|
||||
"""
|
||||
get absolute path for all the given themes, if a theme is no found
|
||||
at multiple places than all paths for the theme will be included.
|
||||
If a theme is not found anywhere then theme will be skipped with
|
||||
an error message printed on the console.
|
||||
|
||||
If themes is 'None' then all themes in given dirs are returned.
|
||||
|
||||
Args:
|
||||
themes (list): list of all theme names
|
||||
theme_dirs (list): list of base dirs that contain themes
|
||||
Returns:
|
||||
list of absolute paths to themes.
|
||||
"""
|
||||
theme_paths = []
|
||||
|
||||
for theme in themes:
|
||||
theme_base_dirs = get_theme_base_dirs(theme, theme_dirs)
|
||||
if not theme_base_dirs:
|
||||
print((
|
||||
"\033[91m\nSkipping '{theme}': \n"
|
||||
"Theme ({theme}) not found in any of the theme dirs ({theme_dirs}). \033[00m".format(
|
||||
theme=theme,
|
||||
theme_dirs=", ".join(theme_dirs)
|
||||
),
|
||||
))
|
||||
theme_paths.extend(theme_base_dirs)
|
||||
|
||||
return theme_paths
|
||||
|
||||
|
||||
def get_theme_base_dirs(theme, theme_dirs):
|
||||
"""
|
||||
Get all base dirs where the given theme can be found.
|
||||
|
||||
Args:
|
||||
theme (str): name of the theme to find
|
||||
theme_dirs (list): list of all base dirs where the given theme could be found
|
||||
|
||||
Returns:
|
||||
list of all the dirs for the goven theme
|
||||
"""
|
||||
theme_paths = []
|
||||
for _dir in theme_dirs:
|
||||
for dir_name in {theme}.intersection(os.listdir(_dir)):
|
||||
if is_theme_dir(Path(_dir) / dir_name):
|
||||
theme_paths.append(Path(_dir) / dir_name)
|
||||
return theme_paths
|
||||
|
||||
|
||||
def is_theme_dir(_dir):
|
||||
"""
|
||||
Returns true if given dir contains theme overrides.
|
||||
A theme dir must have subdirectory 'lms' or 'cms' or both.
|
||||
|
||||
Args:
|
||||
_dir: directory path to check for a theme
|
||||
|
||||
Returns:
|
||||
Returns true if given dir is a theme directory.
|
||||
"""
|
||||
theme_sub_directories = {'lms', 'cms'}
|
||||
return bool(os.path.isdir(_dir) and theme_sub_directories.intersection(os.listdir(_dir)))
|
||||
@@ -2,23 +2,18 @@
|
||||
Asset compilation and collection.
|
||||
"""
|
||||
|
||||
|
||||
import argparse
|
||||
import glob
|
||||
import json
|
||||
import os
|
||||
import shlex
|
||||
import traceback
|
||||
from datetime import datetime
|
||||
from functools import wraps
|
||||
from threading import Timer
|
||||
|
||||
from paver import tasks
|
||||
from paver.easy import call_task, cmdopts, consume_args, needs, no_help, path, sh, task
|
||||
from paver.easy import call_task, cmdopts, consume_args, needs, no_help, sh, task
|
||||
from watchdog.events import PatternMatchingEventHandler
|
||||
from watchdog.observers import Observer
|
||||
from watchdog.observers.api import DEFAULT_OBSERVER_TIMEOUT
|
||||
|
||||
from openedx.core.djangoapps.theming.paver_helpers import get_theme_paths
|
||||
from watchdog.observers import Observer # pylint disable=unused-import # Used by Tutor. Remove after Sumac cut.
|
||||
|
||||
from .utils.cmd import cmd, django_cmd
|
||||
from .utils.envs import Env
|
||||
@@ -38,19 +33,6 @@ SYSTEMS = {
|
||||
'studio': CMS
|
||||
}
|
||||
|
||||
# Common lookup paths that are added to the lookup paths for all sass compilations
|
||||
COMMON_LOOKUP_PATHS = [
|
||||
path("common/static"),
|
||||
path("common/static/sass"),
|
||||
path('node_modules/@edx'),
|
||||
path('node_modules'),
|
||||
]
|
||||
|
||||
# system specific lookup path additions, add sass dirs if one system depends on the sass files for other systems
|
||||
SASS_LOOKUP_DEPENDENCIES = {
|
||||
'cms': [path('lms') / 'static' / 'sass' / 'partials', ],
|
||||
}
|
||||
|
||||
# Collectstatic log directory setting
|
||||
COLLECTSTATIC_LOG_DIR_ARG = 'collect_log_dir'
|
||||
|
||||
@@ -58,242 +40,6 @@ COLLECTSTATIC_LOG_DIR_ARG = 'collect_log_dir'
|
||||
WEBPACK_COMMAND = 'STATIC_ROOT_LMS={static_root_lms} STATIC_ROOT_CMS={static_root_cms} $(npm bin)/webpack {options}'
|
||||
|
||||
|
||||
def get_sass_directories(system, theme_dir=None):
|
||||
"""
|
||||
Determine the set of SASS directories to be compiled for the specified list of system and theme
|
||||
and return a list of those directories.
|
||||
|
||||
Each item in the list is dict object containing the following key-value pairs.
|
||||
{
|
||||
"sass_source_dir": "", # directory where source sass files are present
|
||||
"css_destination_dir": "", # destination where css files would be placed
|
||||
"lookup_paths": [], # list of directories to be passed as lookup paths for @import resolution.
|
||||
}
|
||||
|
||||
if theme_dir is empty or None then return sass directories for the given system only. (i.e. lms or cms)
|
||||
|
||||
:param system: name if the system for which to compile sass e.g. 'lms', 'cms'
|
||||
:param theme_dir: absolute path of theme for which to compile sass files.
|
||||
"""
|
||||
if system not in SYSTEMS:
|
||||
raise ValueError("'system' must be one of ({allowed_values})".format(
|
||||
allowed_values=', '.join(list(SYSTEMS.keys())))
|
||||
)
|
||||
system = SYSTEMS[system]
|
||||
|
||||
applicable_directories = []
|
||||
|
||||
if theme_dir:
|
||||
# Add theme sass directories
|
||||
applicable_directories.extend(
|
||||
get_theme_sass_dirs(system, theme_dir)
|
||||
)
|
||||
else:
|
||||
# add system sass directories
|
||||
applicable_directories.extend(
|
||||
get_system_sass_dirs(system)
|
||||
)
|
||||
|
||||
return applicable_directories
|
||||
|
||||
|
||||
def get_common_sass_directories():
|
||||
"""
|
||||
Determine the set of common SASS directories to be compiled for all the systems and themes.
|
||||
|
||||
Each item in the returned list is dict object containing the following key-value pairs.
|
||||
{
|
||||
"sass_source_dir": "", # directory where source sass files are present
|
||||
"css_destination_dir": "", # destination where css files would be placed
|
||||
"lookup_paths": [], # list of directories to be passed as lookup paths for @import resolution.
|
||||
}
|
||||
"""
|
||||
applicable_directories = []
|
||||
|
||||
# add common sass directories
|
||||
applicable_directories.append({
|
||||
"sass_source_dir": path("common/static/sass"),
|
||||
"css_destination_dir": path("common/static/css"),
|
||||
"lookup_paths": COMMON_LOOKUP_PATHS,
|
||||
})
|
||||
|
||||
return applicable_directories
|
||||
|
||||
|
||||
def get_theme_sass_dirs(system, theme_dir):
|
||||
"""
|
||||
Return list of sass dirs that need to be compiled for the given theme.
|
||||
|
||||
:param system: name if the system for which to compile sass e.g. 'lms', 'cms'
|
||||
:param theme_dir: absolute path of theme for which to compile sass files.
|
||||
"""
|
||||
if system not in ('lms', 'cms'):
|
||||
raise ValueError('"system" must either be "lms" or "cms"')
|
||||
|
||||
dirs = []
|
||||
|
||||
system_sass_dir = path(system) / "static" / "sass"
|
||||
sass_dir = theme_dir / system / "static" / "sass"
|
||||
css_dir = theme_dir / system / "static" / "css"
|
||||
certs_sass_dir = theme_dir / system / "static" / "certificates" / "sass"
|
||||
certs_css_dir = theme_dir / system / "static" / "certificates" / "css"
|
||||
builtin_xblock_sass = path("xmodule") / "assets"
|
||||
|
||||
dependencies = SASS_LOOKUP_DEPENDENCIES.get(system, [])
|
||||
if sass_dir.isdir():
|
||||
css_dir.mkdir_p()
|
||||
|
||||
# first compile lms sass files and place css in theme dir
|
||||
dirs.append({
|
||||
"sass_source_dir": system_sass_dir,
|
||||
"css_destination_dir": css_dir,
|
||||
"lookup_paths": dependencies + [
|
||||
sass_dir / "partials",
|
||||
system_sass_dir / "partials",
|
||||
system_sass_dir,
|
||||
],
|
||||
})
|
||||
|
||||
# now compile theme sass files and override css files generated from lms
|
||||
dirs.append({
|
||||
"sass_source_dir": sass_dir,
|
||||
"css_destination_dir": css_dir,
|
||||
"lookup_paths": dependencies + [
|
||||
sass_dir / "partials",
|
||||
system_sass_dir / "partials",
|
||||
system_sass_dir,
|
||||
],
|
||||
})
|
||||
|
||||
# now compile theme sass files for certificate
|
||||
if system == 'lms':
|
||||
dirs.append({
|
||||
"sass_source_dir": certs_sass_dir,
|
||||
"css_destination_dir": certs_css_dir,
|
||||
"lookup_paths": [
|
||||
sass_dir / "partials",
|
||||
sass_dir
|
||||
],
|
||||
})
|
||||
|
||||
# Now, finally, compile builtin XBlocks' Sass. Themes cannot override these
|
||||
# Sass files directly, but they *can* modify Sass variables which will affect
|
||||
# the output here. We compile all builtin XBlocks' Sass both for LMS and CMS,
|
||||
# not because we expect the output to be different between LMS and CMS, but
|
||||
# because only LMS/CMS-compiled Sass can be themed; common sass is not themed.
|
||||
dirs.append({
|
||||
"sass_source_dir": builtin_xblock_sass,
|
||||
"css_destination_dir": css_dir,
|
||||
"lookup_paths": [
|
||||
# XBlock editor views may need both LMS and CMS partials.
|
||||
# XBlock display views should only need LMS patials.
|
||||
# In order to keep this build script simpler, though, we just
|
||||
# include everything and compile everything at once.
|
||||
theme_dir / "lms" / "static" / "sass" / "partials",
|
||||
theme_dir / "cms" / "static" / "sass" / "partials",
|
||||
path("lms") / "static" / "sass" / "partials",
|
||||
path("cms") / "static" / "sass" / "partials",
|
||||
path("lms") / "static" / "sass",
|
||||
path("cms") / "static" / "sass",
|
||||
],
|
||||
})
|
||||
|
||||
return dirs
|
||||
|
||||
|
||||
def get_system_sass_dirs(system):
|
||||
"""
|
||||
Return list of sass dirs that need to be compiled for the given system.
|
||||
|
||||
:param system: name if the system for which to compile sass e.g. 'lms', 'cms'
|
||||
"""
|
||||
if system not in ('lms', 'cms'):
|
||||
raise ValueError('"system" must either be "lms" or "cms"')
|
||||
|
||||
dirs = []
|
||||
sass_dir = path(system) / "static" / "sass"
|
||||
css_dir = path(system) / "static" / "css"
|
||||
builtin_xblock_sass = path("xmodule") / "assets"
|
||||
|
||||
dependencies = SASS_LOOKUP_DEPENDENCIES.get(system, [])
|
||||
dirs.append({
|
||||
"sass_source_dir": sass_dir,
|
||||
"css_destination_dir": css_dir,
|
||||
"lookup_paths": dependencies + [
|
||||
sass_dir / "partials",
|
||||
sass_dir,
|
||||
],
|
||||
})
|
||||
|
||||
if system == 'lms':
|
||||
dirs.append({
|
||||
"sass_source_dir": path(system) / "static" / "certificates" / "sass",
|
||||
"css_destination_dir": path(system) / "static" / "certificates" / "css",
|
||||
"lookup_paths": [
|
||||
sass_dir / "partials",
|
||||
sass_dir
|
||||
],
|
||||
})
|
||||
|
||||
# See builtin_xblock_sass compilation in get_theme_sass_dirs for details.
|
||||
dirs.append({
|
||||
"sass_source_dir": builtin_xblock_sass,
|
||||
"css_destination_dir": css_dir,
|
||||
"lookup_paths": dependencies + [
|
||||
path("lms") / "static" / "sass" / "partials",
|
||||
path("cms") / "static" / "sass" / "partials",
|
||||
path("lms") / "static" / "sass",
|
||||
path("cms") / "static" / "sass",
|
||||
],
|
||||
})
|
||||
|
||||
return dirs
|
||||
|
||||
|
||||
def get_watcher_dirs(theme_dirs=None, themes=None):
|
||||
"""
|
||||
Return sass directories that need to be added to sass watcher.
|
||||
|
||||
Example:
|
||||
>> get_watcher_dirs('/edx/app/edx-platform/themes', ['red-theme'])
|
||||
[
|
||||
'common/static',
|
||||
'common/static/sass',
|
||||
'lms/static/sass',
|
||||
'lms/static/sass/partials',
|
||||
'/edx/app/edxapp/edx-platform/themes/red-theme/lms/static/sass',
|
||||
'/edx/app/edxapp/edx-platform/themes/red-theme/lms/static/sass/partials',
|
||||
'cms/static/sass',
|
||||
'cms/static/sass/partials',
|
||||
'/edx/app/edxapp/edx-platform/themes/red-theme/cms/static/sass/partials',
|
||||
]
|
||||
|
||||
Parameters:
|
||||
theme_dirs (list): list of theme base directories.
|
||||
themes (list): list containing names of themes
|
||||
Returns:
|
||||
(list): dirs that need to be added to sass watchers.
|
||||
"""
|
||||
dirs = []
|
||||
dirs.extend(COMMON_LOOKUP_PATHS)
|
||||
if theme_dirs and themes:
|
||||
# Register sass watchers for all the given themes
|
||||
themes = get_theme_paths(themes=themes, theme_dirs=theme_dirs)
|
||||
for theme in themes:
|
||||
for _dir in get_sass_directories('lms', theme) + get_sass_directories('cms', theme):
|
||||
dirs.append(_dir['sass_source_dir'])
|
||||
dirs.extend(_dir['lookup_paths'])
|
||||
|
||||
# Register sass watchers for lms and cms
|
||||
for _dir in get_sass_directories('lms') + get_sass_directories('cms') + get_common_sass_directories():
|
||||
dirs.append(_dir['sass_source_dir'])
|
||||
dirs.extend(_dir['lookup_paths'])
|
||||
|
||||
# remove duplicates
|
||||
dirs = list(set(dirs))
|
||||
return dirs
|
||||
|
||||
|
||||
def debounce(seconds=1):
|
||||
"""
|
||||
Prevents the decorated function from being called more than every `seconds`
|
||||
@@ -327,7 +73,6 @@ class SassWatcher(PatternMatchingEventHandler):
|
||||
def register(self, observer, directories):
|
||||
"""
|
||||
register files with observer
|
||||
|
||||
Arguments:
|
||||
observer (watchdog.observers.Observer): sass file observer
|
||||
directories (list): list of directories to be register for sass watcher.
|
||||
@@ -357,8 +102,8 @@ class SassWatcher(PatternMatchingEventHandler):
|
||||
('system=', 's', 'The system to compile sass for (defaults to all)'),
|
||||
('theme-dirs=', '-td', 'Theme dirs containing all themes (defaults to None)'),
|
||||
('themes=', '-t', 'The theme to compile sass for (defaults to None)'),
|
||||
('debug', 'd', 'Debug mode'),
|
||||
('force', '', 'Force full compilation'),
|
||||
('debug', 'd', 'DEPRECATED. Debug mode is now determined by NODE_ENV.'),
|
||||
('force', '', 'DEPRECATED. Full recompilation is now always forced.'),
|
||||
])
|
||||
@timed
|
||||
def compile_sass(options):
|
||||
@@ -396,173 +141,89 @@ def compile_sass(options):
|
||||
compile sass files for cms only for 'red-theme', 'stanford-style' and 'test-theme' present in
|
||||
'/edx/app/edxapp/edx-platform/themes' and '/edx/app/edxapp/edx-platform/common/test/'.
|
||||
|
||||
This is a DEPRECATED COMPATIBILITY WRAPPER. Use `npm run compile-sass` instead.
|
||||
"""
|
||||
debug = options.get('debug')
|
||||
force = options.get('force')
|
||||
systems = get_parsed_option(options, 'system', ALL_SYSTEMS)
|
||||
themes = get_parsed_option(options, 'themes', [])
|
||||
theme_dirs = get_parsed_option(options, 'theme_dirs', [])
|
||||
|
||||
if not theme_dirs and themes:
|
||||
# We can not compile a theme sass without knowing the directory that contains the theme.
|
||||
raise ValueError('theme-dirs must be provided for compiling theme sass.')
|
||||
|
||||
if themes and theme_dirs:
|
||||
themes = get_theme_paths(themes=themes, theme_dirs=theme_dirs)
|
||||
|
||||
# Compile sass for OpenEdx theme after comprehensive themes
|
||||
if None not in themes:
|
||||
themes.append(None)
|
||||
|
||||
timing_info = []
|
||||
dry_run = tasks.environment.dry_run
|
||||
compilation_results = {'success': [], 'failure': []}
|
||||
|
||||
print("\t\tStarted compiling Sass:")
|
||||
|
||||
# compile common sass files
|
||||
is_successful = _compile_sass('common', None, debug, force, timing_info)
|
||||
if is_successful:
|
||||
print("Finished compiling 'common' sass.")
|
||||
compilation_results['success' if is_successful else 'failure'].append('"common" sass files.')
|
||||
|
||||
for system in systems:
|
||||
for theme in themes:
|
||||
print("Started compiling '{system}' Sass for '{theme}'.".format(system=system, theme=theme or 'system'))
|
||||
|
||||
# Compile sass files
|
||||
is_successful = _compile_sass(
|
||||
system=system,
|
||||
theme=path(theme) if theme else None,
|
||||
debug=debug,
|
||||
force=force,
|
||||
timing_info=timing_info
|
||||
)
|
||||
|
||||
if is_successful:
|
||||
print("Finished compiling '{system}' Sass for '{theme}'.".format(
|
||||
system=system, theme=theme or 'system'
|
||||
))
|
||||
|
||||
compilation_results['success' if is_successful else 'failure'].append('{system} sass for {theme}.'.format(
|
||||
system=system, theme=theme or 'system',
|
||||
))
|
||||
|
||||
print("\t\tFinished compiling Sass:")
|
||||
if not dry_run:
|
||||
for sass_dir, css_dir, duration in timing_info:
|
||||
print(f">> {sass_dir} -> {css_dir} in {duration}s")
|
||||
|
||||
if compilation_results['success']:
|
||||
print("\033[92m\nSuccessful compilations:\n--- " + "\n--- ".join(compilation_results['success']) + "\n\033[00m")
|
||||
if compilation_results['failure']:
|
||||
print("\033[91m\nFailed compilations:\n--- " + "\n--- ".join(compilation_results['failure']) + "\n\033[00m")
|
||||
systems = set(get_parsed_option(options, 'system', ALL_SYSTEMS))
|
||||
command = shlex.join(
|
||||
[
|
||||
"npm",
|
||||
"run",
|
||||
"compile-sass",
|
||||
"--",
|
||||
*(["--dry"] if tasks.environment.dry_run else []),
|
||||
*(["--skip-lms"] if not systems & {"lms"} else []),
|
||||
*(["--skip-cms"] if not systems & {"cms", "studio"} else []),
|
||||
*(
|
||||
arg
|
||||
for theme_dir in get_parsed_option(options, 'theme_dirs', [])
|
||||
for arg in ["--theme-dir", str(theme_dir)]
|
||||
),
|
||||
*(
|
||||
arg
|
||||
for theme in get_parsed_option(options, "theme", [])
|
||||
for arg in ["--theme", theme]
|
||||
),
|
||||
]
|
||||
)
|
||||
depr_warning = (
|
||||
"\n" +
|
||||
"⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ \n" +
|
||||
"\n" +
|
||||
"WARNING: 'paver compile_sass' is DEPRECATED! It will be removed before Sumac.\n" +
|
||||
"The command you ran is now just a temporary wrapper around a new,\n" +
|
||||
"supported command, which you should use instead:\n" +
|
||||
"\n" +
|
||||
f"\t{command}\n" +
|
||||
"\n" +
|
||||
"Details: https://github.com/openedx/edx-platform/issues/31895\n" +
|
||||
"\n" +
|
||||
("WARNING: ignoring deprecated flag '--debug'\n" if options.get("debug") else "") +
|
||||
("WARNING: ignoring deprecated flag '--force'\n" if options.get("force") else "") +
|
||||
"⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ \n" +
|
||||
"\n"
|
||||
)
|
||||
# Print deprecation warning twice so that it's more likely to be seen in the logs.
|
||||
print(depr_warning)
|
||||
sh(command)
|
||||
print(depr_warning)
|
||||
|
||||
|
||||
def _compile_sass(system, theme, debug, force, timing_info):
|
||||
def _compile_sass(system, theme, _debug, _force, _timing_info):
|
||||
"""
|
||||
Compile sass files for the given system and theme.
|
||||
This is a DEPRECATED COMPATIBILITY WRAPPER
|
||||
|
||||
:param system: system to compile sass for e.g. 'lms', 'cms', 'common'
|
||||
:param theme: absolute path of the theme to compile sass for.
|
||||
:param debug: boolean showing whether to display source comments in resulted css
|
||||
:param force: boolean showing whether to remove existing css files before generating new files
|
||||
:param timing_info: list variable to keep track of timing for sass compilation
|
||||
It exists to ease the transition for Tutor in Redwood, which directly imported and used this function.
|
||||
"""
|
||||
|
||||
# Note: import sass only when it is needed and not at the top of the file.
|
||||
# This allows other paver commands to operate even without libsass being
|
||||
# installed. In particular, this allows the install_prereqs command to be
|
||||
# used to install the dependency.
|
||||
import sass
|
||||
if system == "common":
|
||||
sass_dirs = get_common_sass_directories()
|
||||
else:
|
||||
sass_dirs = get_sass_directories(system, theme)
|
||||
|
||||
dry_run = tasks.environment.dry_run
|
||||
|
||||
# determine css out put style and source comments enabling
|
||||
if debug:
|
||||
source_comments = True
|
||||
output_style = 'nested'
|
||||
else:
|
||||
source_comments = False
|
||||
output_style = 'compressed'
|
||||
|
||||
for dirs in sass_dirs:
|
||||
start = datetime.now()
|
||||
css_dir = dirs['css_destination_dir']
|
||||
sass_source_dir = dirs['sass_source_dir']
|
||||
lookup_paths = dirs['lookup_paths']
|
||||
|
||||
if not sass_source_dir.isdir():
|
||||
print("\033[91m Sass dir '{dir}' does not exists, skipping sass compilation for '{theme}' \033[00m".format(
|
||||
dir=sass_source_dir, theme=theme or system,
|
||||
))
|
||||
# theme doesn't override sass directory, so skip it
|
||||
continue
|
||||
|
||||
if force:
|
||||
if dry_run:
|
||||
tasks.environment.info("rm -rf {css_dir}/*.css".format(
|
||||
css_dir=css_dir,
|
||||
))
|
||||
else:
|
||||
sh(f"rm -rf {css_dir}/*.css")
|
||||
|
||||
all_lookup_paths = COMMON_LOOKUP_PATHS + lookup_paths
|
||||
print(f"Compiling Sass: {sass_source_dir} -> {css_dir}")
|
||||
for lookup_path in all_lookup_paths:
|
||||
print(f" with Sass lookup path: {lookup_path}")
|
||||
if dry_run:
|
||||
tasks.environment.info("libsass {sass_dir}".format(
|
||||
sass_dir=sass_source_dir,
|
||||
))
|
||||
else:
|
||||
sass.compile(
|
||||
dirname=(sass_source_dir, css_dir),
|
||||
include_paths=all_lookup_paths,
|
||||
source_comments=source_comments,
|
||||
output_style=output_style,
|
||||
)
|
||||
|
||||
# For Sass files without explicit RTL versions, generate
|
||||
# an RTL version of the CSS using the rtlcss library.
|
||||
for sass_file in glob.glob(sass_source_dir + '/**/*.scss'):
|
||||
if should_generate_rtl_css_file(sass_file):
|
||||
source_css_file = sass_file.replace(sass_source_dir, css_dir).replace('.scss', '.css')
|
||||
target_css_file = source_css_file.replace('.css', '-rtl.css')
|
||||
sh("rtlcss {source_file} {target_file}".format(
|
||||
source_file=source_css_file,
|
||||
target_file=target_css_file,
|
||||
))
|
||||
|
||||
# Capture the time taken
|
||||
if not dry_run:
|
||||
duration = datetime.now() - start
|
||||
timing_info.append((sass_source_dir, css_dir, duration))
|
||||
return True
|
||||
|
||||
|
||||
def should_generate_rtl_css_file(sass_file):
|
||||
"""
|
||||
Returns true if a Sass file should have an RTL version generated.
|
||||
"""
|
||||
# Don't generate RTL CSS for partials
|
||||
if path(sass_file).name.startswith('_'):
|
||||
return False
|
||||
|
||||
# Don't generate RTL CSS if the file is itself an RTL version
|
||||
if sass_file.endswith('-rtl.scss'):
|
||||
return False
|
||||
|
||||
# Don't generate RTL CSS if there is an explicit Sass version for RTL
|
||||
rtl_sass_file = path(sass_file.replace('.scss', '-rtl.scss'))
|
||||
if rtl_sass_file.exists():
|
||||
return False
|
||||
|
||||
return True
|
||||
command = shlex.join(
|
||||
[
|
||||
"npm",
|
||||
"run",
|
||||
"compile-sass",
|
||||
"--",
|
||||
*(["--dry"] if tasks.environment.dry_run else []),
|
||||
*(["--skip-default", "--theme-dir", str(theme.parent), "--theme", str(theme.name)] if theme else []),
|
||||
("--skip-cms" if system == "lms" else "--skip-lms"),
|
||||
]
|
||||
)
|
||||
depr_warning = (
|
||||
"\n" +
|
||||
"⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ \n" +
|
||||
"\n" +
|
||||
"WARNING: 'pavelib/assets.py' is DEPRECATED! It will be removed before Sumac.\n" +
|
||||
"The function you called is just a temporary wrapper around a new, supported command,\n" +
|
||||
"which you should use instead:\n" +
|
||||
"\n" +
|
||||
f"\t{command}\n" +
|
||||
"\n" +
|
||||
"Details: https://github.com/openedx/edx-platform/issues/31895\n" +
|
||||
"\n" +
|
||||
"⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ \n" +
|
||||
"\n"
|
||||
)
|
||||
# Print deprecation warning twice so that it's more likely to be seen in the logs.
|
||||
print(depr_warning)
|
||||
sh(command)
|
||||
print(depr_warning)
|
||||
|
||||
|
||||
def process_npm_assets():
|
||||
@@ -582,18 +243,6 @@ def process_xmodule_assets():
|
||||
print("\t\tWhen paver is removed from edx-platform, this step will not replaced.")
|
||||
|
||||
|
||||
def restart_django_servers():
|
||||
"""
|
||||
Restart the django server.
|
||||
|
||||
`$ touch` makes the Django file watcher thinks that something has changed, therefore
|
||||
it restarts the server.
|
||||
"""
|
||||
sh(cmd(
|
||||
"touch", 'lms/urls.py', 'cms/urls.py',
|
||||
))
|
||||
|
||||
|
||||
def collect_assets(systems, settings, **kwargs):
|
||||
"""
|
||||
Collect static assets, including Django pipeline processing.
|
||||
@@ -744,55 +393,57 @@ def listfy(data):
|
||||
|
||||
@task
|
||||
@cmdopts([
|
||||
('background', 'b', 'Background mode'),
|
||||
('settings=', 's', "Django settings (defaults to devstack)"),
|
||||
('background', 'b', 'DEPRECATED. Use shell tools like & to run in background if needed.'),
|
||||
('settings=', 's', "DEPRECATED. Django is not longer invoked to compile JS/Sass."),
|
||||
('theme-dirs=', '-td', 'The themes dir containing all themes (defaults to None)'),
|
||||
('themes=', '-t', 'The themes to add sass watchers for (defaults to None)'),
|
||||
('wait=', '-w', 'How long to pause between filesystem scans.')
|
||||
('themes=', '-t', 'DEPRECATED. All themes in --theme-dirs are now watched.'),
|
||||
('wait=', '-w', 'DEPRECATED. Watchdog\'s default wait time is now used.'),
|
||||
])
|
||||
@timed
|
||||
def watch_assets(options):
|
||||
"""
|
||||
Watch for changes to asset files, and regenerate js/css
|
||||
|
||||
This is a DEPRECATED COMPATIBILITY WRAPPER. Use `npm run watch` instead.
|
||||
"""
|
||||
# Don't watch assets when performing a dry run
|
||||
if tasks.environment.dry_run:
|
||||
return
|
||||
|
||||
settings = getattr(options, 'settings', Env.DEVSTACK_SETTINGS)
|
||||
|
||||
themes = get_parsed_option(options, 'themes')
|
||||
theme_dirs = get_parsed_option(options, 'theme_dirs', [])
|
||||
|
||||
default_wait = [str(DEFAULT_OBSERVER_TIMEOUT)]
|
||||
wait = float(get_parsed_option(options, 'wait', default_wait)[0])
|
||||
|
||||
if not theme_dirs and themes: # lint-amnesty, pylint: disable=no-else-raise
|
||||
# We can not add theme sass watchers without knowing the directory that contains the themes.
|
||||
raise ValueError('theme-dirs must be provided for watching theme sass.')
|
||||
else:
|
||||
theme_dirs = [path(_dir) for _dir in theme_dirs]
|
||||
|
||||
sass_directories = get_watcher_dirs(theme_dirs, themes)
|
||||
observer = Observer(timeout=wait)
|
||||
|
||||
SassWatcher().register(observer, sass_directories)
|
||||
|
||||
print("Starting asset watcher...")
|
||||
observer.start()
|
||||
|
||||
# Run the Webpack file system watcher too
|
||||
execute_webpack_watch(settings=settings)
|
||||
|
||||
if not getattr(options, 'background', False):
|
||||
# when running as a separate process, the main thread needs to loop
|
||||
# in order to allow for shutdown by control-c
|
||||
try:
|
||||
while True:
|
||||
observer.join(2)
|
||||
except KeyboardInterrupt:
|
||||
observer.stop()
|
||||
print("\nStopped asset watcher.")
|
||||
theme_dirs = ':'.join(get_parsed_option(options, 'theme_dirs', []))
|
||||
command = shlex.join(
|
||||
[
|
||||
*(
|
||||
["env", f"EDX_PLATFORM_THEME_DIRS={theme_dirs}"] if theme_dirs else []
|
||||
),
|
||||
"npm",
|
||||
"run",
|
||||
"watch",
|
||||
]
|
||||
)
|
||||
depr_warning = (
|
||||
"\n" +
|
||||
"⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ \n" +
|
||||
"\n" +
|
||||
"WARNING: 'paver watch_assets' is DEPRECATED! It will be removed before Sumac.\n" +
|
||||
"The command you ran is now just a temporary wrapper around a new,\n" +
|
||||
"supported command, which you should use instead:\n" +
|
||||
"\n" +
|
||||
f"\t{command}\n" +
|
||||
"\n" +
|
||||
"Details: https://github.com/openedx/edx-platform/issues/31895\n" +
|
||||
"\n" +
|
||||
("WARNING: ignoring deprecated flag '--debug'\n" if options.get("debug") else "") +
|
||||
("WARNING: ignoring deprecated flag '--themes'\n" if options.get("themes") else "") +
|
||||
("WARNING: ignoring deprecated flag '--settings'\n" if options.get("settings") else "") +
|
||||
("WARNING: ignoring deprecated flag '--background'\n" if options.get("background") else "") +
|
||||
"⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ \n" +
|
||||
"\n"
|
||||
)
|
||||
# Print deprecation warning twice so that it's more likely to be seen in the logs.
|
||||
print(depr_warning)
|
||||
sh(command)
|
||||
print(depr_warning)
|
||||
|
||||
|
||||
@task
|
||||
|
||||
@@ -8,7 +8,6 @@ from unittest.mock import patch
|
||||
import ddt
|
||||
import paver.tasks
|
||||
from paver.easy import call_task, path
|
||||
from watchdog.observers import Observer
|
||||
|
||||
from pavelib.assets import COLLECTSTATIC_LOG_DIR_ARG, collect_assets
|
||||
|
||||
@@ -197,86 +196,6 @@ class TestPaverThemeAssetTasks(PaverTestCase):
|
||||
assert len(self.task_messages) == len(expected_messages)
|
||||
|
||||
|
||||
class TestPaverWatchAssetTasks(TestCase):
|
||||
"""
|
||||
Test the Paver watch asset tasks.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.expected_sass_directories = [
|
||||
path('common/static/sass'),
|
||||
path('common/static'),
|
||||
path('node_modules/@edx'),
|
||||
path('node_modules'),
|
||||
path('lms/static/sass/partials'),
|
||||
path('lms/static/sass'),
|
||||
path('lms/static/certificates/sass'),
|
||||
path('cms/static/sass'),
|
||||
path('cms/static/sass/partials'),
|
||||
]
|
||||
|
||||
# Reset the options that paver stores in a global variable (thus polluting tests)
|
||||
if 'pavelib.assets.watch_assets' in paver.tasks.environment.options:
|
||||
del paver.tasks.environment.options['pavelib.assets.watch_assets']
|
||||
|
||||
super().setUp()
|
||||
|
||||
def tearDown(self):
|
||||
self.expected_sass_directories = []
|
||||
super().tearDown()
|
||||
|
||||
def test_watch_assets(self):
|
||||
"""
|
||||
Test the "compile_sass" task.
|
||||
"""
|
||||
with patch('pavelib.assets.SassWatcher.register') as mock_register:
|
||||
with patch('pavelib.assets.Observer.start'):
|
||||
with patch('pavelib.assets.execute_webpack_watch') as mock_webpack:
|
||||
call_task(
|
||||
'pavelib.assets.watch_assets',
|
||||
options={"background": True},
|
||||
)
|
||||
assert mock_register.call_count == 2
|
||||
assert mock_webpack.call_count == 1
|
||||
|
||||
sass_watcher_args = mock_register.call_args_list[0][0]
|
||||
|
||||
assert isinstance(sass_watcher_args[0], Observer)
|
||||
assert isinstance(sass_watcher_args[1], list)
|
||||
assert len(sass_watcher_args[1]) == len(self.expected_sass_directories)
|
||||
|
||||
def test_watch_theme_assets(self):
|
||||
"""
|
||||
Test the Paver watch asset tasks with theming enabled.
|
||||
"""
|
||||
self.expected_sass_directories.extend([
|
||||
path(TEST_THEME_DIR) / 'lms/static/sass',
|
||||
path(TEST_THEME_DIR) / 'lms/static/sass/partials',
|
||||
path(TEST_THEME_DIR) / 'lms/static/certificates/sass',
|
||||
path(TEST_THEME_DIR) / 'cms/static/sass',
|
||||
path(TEST_THEME_DIR) / 'cms/static/sass/partials',
|
||||
])
|
||||
|
||||
with patch('pavelib.assets.SassWatcher.register') as mock_register:
|
||||
with patch('pavelib.assets.Observer.start'):
|
||||
with patch('pavelib.assets.execute_webpack_watch') as mock_webpack:
|
||||
call_task(
|
||||
'pavelib.assets.watch_assets',
|
||||
options={
|
||||
"background": True,
|
||||
"theme_dirs": [TEST_THEME_DIR.dirname()],
|
||||
"themes": [TEST_THEME_DIR.basename()]
|
||||
},
|
||||
)
|
||||
assert mock_register.call_count == 2
|
||||
assert mock_webpack.call_count == 1
|
||||
|
||||
sass_watcher_args = mock_register.call_args_list[0][0]
|
||||
assert isinstance(sass_watcher_args[0], Observer)
|
||||
assert isinstance(sass_watcher_args[1], list)
|
||||
assert len(sass_watcher_args[1]) == len(self.expected_sass_directories)
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class TestCollectAssets(PaverTestCase):
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user